package org.ithasu.tools.bugOne;

import gnu.io.SerialPort;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.TooManyListenersException;

import org.ithasu.tools.Console;
import org.ithasu.tools.IOTools;

public class SerialSniffer implements SerialPortEventListener {

	// Receive
	private final ArrayList<MessageListener> listeners = new ArrayList<MessageListener>();
	private byte[] data = null;
	private int dataIndex = 0;
	private byte checksum = 0x00;

	// Serial port
	private SerialPort port = null;
	private InputStream input = null;
	private OutputStream output = null;

	public SerialSniffer() {
	}

	// -- Connect ----------------------------------------

	public SerialPort getSerialPort() {
		return port;
	}

	public boolean connect(String device, int baudrate) {
		disconnect();
		port = IOTools.serial_connect(device, baudrate);
		if (port == null) {
			return false;
		}

		try {
			input = port.getInputStream();
			output = port.getOutputStream();
		} catch (IOException e) {
			disconnect();
			return false;
		}

		try {
			port.addEventListener(this);
			port.notifyOnDataAvailable(true);
		} catch (TooManyListenersException e) {
			disconnect();
			return false;
		}

		return true;
	}

	public void disconnect() {
		if (port != null) {
			port.close();
		}

		try {
			if (input != null) {
				input.close();
			}
			if (output != null) {
				output.close();
			}
		} catch (IOException e) {
		}

		port = null;
		input = null;
		output = null;
		clearMessageBuffer();
	}

	// -- Receive ----------------------------------------

	public void addMessageListener(MessageListener listener) {
		listeners.add(listener);
	}

	@Override
	public void serialEvent(SerialPortEvent arg0) {
		// Read from serial input
		try {
			while (input.available() != 0) {
				int c = input.read();
				if (c < 0) {
					Console.debug("Lost connection");
					disconnect();
				} else {
					addToMessageBuffer(c);
				}
			}
		} catch (IOException e) {
			Console.error(e);
		}
	}

	protected void fireMessageReceived(byte[] message) {
		for (MessageListener l : listeners) {
			l.onMessageReceived(this, message);
		}
	}

	private void clearMessageBuffer() {
		Console.debug("Clearing packet");
		data = null;
		dataIndex = 0;
		checksum = 0x00;
	}

	private void addToMessageBuffer(int b) {
		if (data == null) {
			Console.debug("New packet");
			if (b > 0) {
				data = new byte[b];
			} else {
				Console.error(new IllegalStateException("Wrong packet size: "
						+ b));
				return;
			}
		} else if (dataIndex < data.length) {
			byte bb = IOTools.toByte(b);
			data[dataIndex++] = bb;
			checksum ^= bb;
		} else if (dataIndex == data.length) {
			if (checksum == IOTools.toByte(b)) {
				fireMessageReceived(data);
				clearMessageBuffer();
			} else {
				// Wrong message, trash
				Console.warning("Wrong packet");
				fireMessageReceived(data);
				clearMessageBuffer();
			}
		} else {
			Console.error(new IllegalStateException());
			clearMessageBuffer();
		}
	}

	// -- Send ----------------------------------------

	public boolean send(byte[] data) {
		if (output == null) {
			return false;
		}

		if (data == null || data.length > 255) {
			Console.error(new IllegalStateException(
					"Packet is too long (size > 255)"));
			return false;
		}
		// Prepare packet
		byte[] packet = new byte[data.length + 2];
		packet[0] = IOTools.toByte(data.length);
		byte checksum = 0x00;
		for (int i = 0; i < data.length; i++) {
			byte c = data[i];
			packet[i + 1] = c;
			checksum ^= c;
		}
		packet[packet.length - 1] = checksum;

		try {
			output.write(packet);
			output.flush();
		} catch (IOException e) {
			Console.error(e);
			return false;
		}

		return true;
	}

	// -- Listener ----------------------------------------

	public static interface MessageListener {
		public void onMessageReceived(SerialSniffer sniffer, byte[] message);
	}

}
